namespace Vsed {

errordomain Exception
{	E
}

const string VALA =
"""	public int main(string[] args)
	{	vsed_start_stack_init();
#		if ! VSED_WITH_CATCH
			%s
			yylex();
			%s
#		else
			try
			{	%s
				yylex();
				%s
			}
			catch (GLib.Error error)
			{	%s
			}
#		endif
		return 0;
	}

#	if ! VSED_WITH_CATCH_PUTS
		public inline void puts(string s)
#	else
		public inline void puts(string s) throws GLib.Error
#	endif
	{	%s
	}
#	if ! VSED_WITH_CATCH_PUTC
		public inline void putc(char c)
#	else
		public inline void putc(char c) throws GLib.Error
#	endif
	{	%s
	}
#	if ! VSED_WITH_CATCH_PRINTF
		public void printf(string format, ...)
#	else
		public void printf(string format, ...) throws GLib.Error
#	endif
	{	%s
	}
#	if ! VSED_WITH_CATCH_WRITE
		public void write(string data, int n_bytes)
#	else
		public void write(string data, int n_bytes) throws GLib.Error
#	endif
	{	%s
	}

	public void exit(int status=0, string? err_msg=null, ...)
	{	if (err_msg!=null && err_msg!="")
		{	var args = va_list();
			if (status != 0)
			{	stderr.printf("Error %%d: ", status);
			}
			stderr.vprintf(err_msg, args);
			stderr.puts("\n");
		}
		else if (status != 0)
		{	stderr.printf("Error %%d\n", status);
		}
		Process.exit(status);
	}

#	if ! VSED_WITH_CATCH
		public FileStream fopen(string filename, string mode="r")
		{	var fh = FileStream.open(filename, mode);
			if (fh == null)
			{	exit(1, "%%s", strerror(errno));
			}
			return fh;
		}
		public void push_buffer_file(string filename, int size=1024)
		{	var fh = fopen(filename);
			push_buffer(new Buffer((owned)fh, size));
		}
		public Buffer? pop_buffer()
		{	if (buffers_len <= 0)
			{	exit(1, "pop_buffer(): no items on stack\n");
				return null;
			}
			buffers_len--;
			if (buffers_len > 0)
			{	switch_to_buffer(buffers[buffers_len - 1]);
				return (owned)buffers[buffers_len];
			}
			else
			{	return null;
			}
		}
		public inline void echo(string text, int text_len)
#	else
		public FileStream fopen(string filename, string mode="r") throws GLib.FileError
		{	var fh = FileStream.open(filename, mode);
			if (fh == null)
			{	throw new GLib.FileError.FAILED(strerror(errno));
			}
			return fh;
		}
		public void push_buffer_file(string filename, int size=1024) throws GLib.FileError
		{	var fh = fopen(filename);
			push_buffer(new Buffer((owned)fh, size));
		}
		public Buffer? pop_buffer() throws GLib.FileError
		{	if (buffers_len <= 0)
			{	throw new GLib.FileError.FAILED("pop_buffer(): no items on stack\n");
			}
			buffers_len--;
			if (buffers_len > 0)
			{	switch_to_buffer(buffers[buffers_len - 1]);
				return (owned)buffers[buffers_len];
			}
			else
			{	return null;
			}
		}
		public inline void echo(string text, int text_len) throws GLib.Error
#	endif
	{	%s
	}

	/**	I implement my version of yypush_buffer_state() / yypop_buffer_state() because
	 *	they don't support yy_scan_string().
	 **/
	private Buffer[] buffers;
	public int buffers_len;

	public void push_buffer(owned Buffer buffer)
	{	switch_to_buffer(buffer);
		if (buffers.length <= buffers_len)
		{	buffers += (owned)buffer;
		}
		else
		{	buffers[buffers_len] = (owned)buffer;
		}
		buffers_len++;
	}
	public void push_buffer_stream(owned GLib.FileStream stream, int size=1024)
	{	push_buffer(new Buffer((owned)stream, size));
	}
	public void push_buffer_string(string str)
	{	push_buffer(new Buffer.string(str));
	}
	public void push_buffer_bytes(char[] bytes)
	{	push_buffer(new Buffer.bytes(bytes));
	}
	public void push_buffer_buffer(char[] buffer)
	{	push_buffer(new Buffer.buffer(buffer));
	}
""";

const string VAPI =
"""	[CCode(cheader_filename = "%s")]
	namespace Vsed
	{	[Compact]
		[CCode(cname="struct yy_buffer_state", cprefix="", free_function="vsed_delete_buffer")]
		public class Buffer
		{	[CCode (cname="yy_create_buffer")]
			public Buffer(owned GLib.FileStream stream, int size=1024);

			[CCode (cname="yy_scan_string")]
			public Buffer.string(string str);

			[CCode (cname="yy_scan_bytes")]
			public Buffer.bytes(char[] bytes);

			[CCode (cname="yy_scan_bytes")]
			public Buffer.buffer(char[] buffer);
		}

		[CCode(cname = "vsed_fwrite")]
		public size_t fwrite(string data, size_t n_bytes, GLib.FileStream stream);

		[CCode(cname="int", cprefix = "")]
		public enum S {%s}

		[CCode(cname = "YY_START")]
		public S state;

		[CCode(cname = "vsed_state_name")]
		public unowned string state_name();

		[CCode(cname = "yylineno")]
		public int lineno;

		[CCode(cname = "yylex")]
		public int yylex()%s;

		[CCode(cname = "input")]
		public int input();

		[CCode(cname = "unput")]
		public void unput();

		[CCode(cname = "yyless")]
		public void less(int leave_chars=0);

		[CCode(cname = "vsed_start_stack_init")]
		public void vsed_start_stack_init();

		[CCode(cname = "vsed_start_stack_push")]
		public void push(S state);

		[CCode(cname = "vsed_start_stack_pop")]
		public void pop(int n_pop=1);

		[CCode(cname = "yy_switch_to_buffer")]
		public void switch_to_buffer(Buffer buffer);

		[CCode(cname = "vsed_trace_action")]
		public void trace_action(string source_name, int line, int line_char);

		[CCode(cname = "vsed_flex_debug")]
		public bool flex_debug;
	}
""";

const string FLEX_HEADER =
"""%option noyywrap $(flex_options)
%{
	/**	The same as yy_delete_buffer(), but fcloses FILE associated with this buffer.
	 **/
	void vsed_delete_buffer(struct yy_buffer_state *buffer)
	{	if (buffer->yy_input_file != NULL)
		{	fclose(buffer->yy_input_file);
		}
		yy_delete_buffer(buffer);
	}

	inline size_t vsed_fwrite(const gchar* data, size_t n_bytes, FILE *stream)
	{	return fwrite(data, 1, n_bytes, stream);
	}

	const char *vsed_state_name();

	/* I implement my version of yy_push_state()/yy_pop_state() to provide length variable (vsed_start_stack_len) */
	static int vsed_start_stack_len;
	inline void vsed_start_stack_init();
	inline void vsed_start_stack_push(int new_state);
	inline void vsed_start_stack_pop(int n_pop);

	static int vsed_regex_len;

	$(define)

	#ifdef VSED_FLEX_DEBUG
		int vsed_flex_debug = 1;

		void vsed_trace_action(const char *source_name, int line, int line_char)
		{	if (vsed_flex_debug)
			{	fprintf(stderr, "--accepting rule at %s(%d:%d) in state %s (\"%s\")\n", source_name, line, line_char, vsed_state_name(), yytext);
			}
		}

		void vsed_trace_meta(int pattern_id)
		{	if (vsed_flex_debug)
			{	fprintf(stderr, "--metarule in state VSED_SCANNER_%d (\"%s\")\n", pattern_id, yytext);
			}
		}
	#else
		int vsed_flex_debug = 0;

		#define vsed_trace_action(source_name, line, line_char)
		#define vsed_trace_meta(pattern_id)
	#endif

	static GError *vsed_error = NULL;

	void vsed_catch_intermed(void);

	#ifndef VSED_CATCH_INTERMED
		#define VSED_HANDLE_ERROR \
			if (vsed_error != NULL) \
			{	g_propagate_error(_vsed_arg_error, vsed_error); \
				vsed_error = NULL; \
				return 1; \
			}
	#else
		#define VSED_HANDLE_ERROR \
			if (vsed_error != NULL) \
			{	vsed_catch_intermed(); \
				if (vsed_error != NULL) \
				{	g_propagate_error(_vsed_arg_error, vsed_error); \
					vsed_error = NULL; \
					return 1; \
				} \
			}
	#endif
""";

const string FLEX_FOOTER =
"""
	void vsed_exit(gint status, const gchar* err_msg, ...);

	/* BEGIN start_stack */
	/* I implement my version of yy_push_state()/yy_pop_state() to provide length variable (vsed_start_stack_len) */

	enum {VSED_START_STACK_GROW_BY=25};
	static vsed_start_stack_size, *vsed_start_stack;

	inline void vsed_start_stack_init()
	{	vsed_start_stack_len = 0;
		vsed_start_stack_size = VSED_START_STACK_GROW_BY;
		vsed_start_stack = (int*)malloc(VSED_START_STACK_GROW_BY*sizeof(int));
		vsed_start_stack[0] = INITIAL;
	}

	inline void vsed_start_stack_push(int new_state)
	{	if (++vsed_start_stack_len >= vsed_start_stack_size)
		{	vsed_start_stack_size += VSED_START_STACK_GROW_BY;
			vsed_start_stack = (int*)realloc(vsed_start_stack, vsed_start_stack_size);
		}
		vsed_start_stack[vsed_start_stack_len] = new_state;
		BEGIN(new_state);
	}

	inline void vsed_start_stack_pop(int n_pop)
	{	int old_state;
		vsed_start_stack_len -= n_pop;
		if (vsed_start_stack_len < 0)
		{	vsed_exit(1, "Start-condition stack underflow");
		}
		old_state = vsed_start_stack[vsed_start_stack_len];
		BEGIN(old_state);
	}
	/* END start_stack */
""";

const string MAKEFILE =
"""all : $(prog_name)

$(prog_name).yy.c : $(prog_name).flex
	$(run_lex)

$(prog_name) : $(prog_name).vala $(prog_name).vapi $(prog_name).yy.c
	$(run_valac)

clean :
	rm -f $(prog_name) $(prog_name).flex $(prog_name).vala $(prog_name).vapi $(prog_name).yy.c

install :
	cp $(prog_name) /usr/bin/
	chmod a+x /usr/bin/$(prog_name)

debug :
	$(run_valac) -C
	cc -I. -g `pkg-config --cflags glib-2.0 gobject-2.0 $(pkgs)` $(prog_name).c `pkg-config --libs glib-2.0 gobject-2.0 $(pkgs)` -o $(prog_name)
""";



struct Flags
{	public bool w;
	public bool b;
	public bool s;
	public bool e;
	public bool x;
	public bool i;

	public Flags(string? flags_str=null)
	{	w = b = s = e = x = i = false;
		if (flags_str != null)
		{	for (int i=0; flags_str[i]!='\0'; i++)
			{	try {apply("", flags_str[i], true);} catch {}
			}
		}
	}

	public void apply(string pattern, char c, bool is_positive=true) throws Exception
	{	switch (c)
		{	case 'w': case 'W': w = is_positive; break;
			case 'b': case 'B': b = is_positive; break;
			case 's': case 'S': s = is_positive; break;
			case 'e': case 'E': e = is_positive; break;
			case 'x': case 'X': x = is_positive; break;
			case 'i': case 'I': i = is_positive; break;
			default: throw new Exception.E(@"Unsupported flag '$c': $pattern");
		}
	}

	public void to_pattern(StringBuilder sb)
	{	if (!s && !i && !x)
		{	sb.append("-s");
		}
		else
		{	if (s) sb.append_c('s');
			if (i) sb.append_c('i');
			if (x) sb.append_c('x');
		}
		sb.append_c(':');
	}
}

class Pattern
{	private string? code = null; // isset only in toplevel
	private string? name = null; // isset only in children that have name
	private bool is_owned = false; // isset only in children that have name marked as owned
	private int offset_from = 0; // offset in toplevel.code where my code starts; on toplevel is 0
	private int offset_to = 0; // offset in toplevel.code where my code ends; on toplevel is code.length
	private string? start_conditions = null; // isset only in toplevel; null - no start conditions
	private Flags flags; // flags to wrap pattern in
	private int var_id = 0; // unique var ID over all references under common parent (vsed_text_1, vsed_text_2, etc.)
	private int max_var_id = 0; // isset only in toplevel - counter for var IDs + max var_id over all references under this toplevel pattern
	private static int max_var_id_overall = 0; // max max_var_id over all patterns
	private int id = 0; // unique pattern ID over all references
	private static int enum_ids = 0; // (static) counter for IDs
	private weak Pattern? parent;
	private weak Pattern? top;
	private Pattern[] references = {};
	private string? source_name = null;
	private int line = 0;
	private int line_char = 0;

	public bool is_eof_rule = false; // isset only in toplevel

	private Pattern(Pattern? parent, string? source_name=null, int line=0, int line_char=0)
	{	enum_ids++;
		id = enum_ids;
		this.parent = parent;
		top = parent==null ? this : parent.top;
		this.source_name = source_name;
		this.line = line;
		this.line_char = line_char;
	}

	private void alloc_var_id()
	{	assert(var_id == 0);
		var_id = ++top.max_var_id;
		if (var_id > max_var_id_overall)
		{	max_var_id_overall = var_id;
		}
	}

	public string? debug(StringBuilder? psb=null)
	{	StringBuilder? vsb = null;
		unowned StringBuilder sb;
		if (psb != null)
		{	sb = psb;
		}
		else
		{	vsb = new StringBuilder();
			sb = vsb;
		}
		sb.append_printf
		(	"<pattern name=\"%s\" is_owned=\"%c\" offset_from=\"%d\" offset_to=\"%d\" var_id=\"%d\" id=\"%d\" flags=\"",
			name, is_owned ? '1' : '0', offset_from, offset_to, var_id, id
		);
		flags.to_pattern(sb);
		sb.append("\">");
		if (code != null)
		{	sb.append_printf("<code>%s</code>", code);
		}
		if (start_conditions != null)
		{	sb.append_printf("<start_conditions>%s</start_conditions>", start_conditions);
		}
		for (int i=0; i<references.length; i++)
		{	references[i].debug(sb);
		}
		sb.append("</pattern>");
		return psb==null ? sb.str : null;
	}

	public int get_id()
	{	return id;
	}

	public string get_code(string? all_start_conditions=null, bool as_top=true) throws Exception
	{	unowned string tcode = top.code;
		var sb = new StringBuilder();
		if (as_top)
		{	if (top.start_conditions!=null && all_start_conditions!=null)
			{	if (top.start_conditions == "*")
				{	sb.append_printf("<%s>", all_start_conditions);
				}
				else
				{	sb.append_printf("<%s>", top.start_conditions);
				}
			}
			if (top.is_eof_rule)
			{	sb.append("<<EOF>>");
				return sb.str;
			}
			var flags = top.flags;
			var code = top.code;
			var len = code.length;
			if (code[0]!='^' && code[len-1]!='$')
			{	sb.append("(?");
				flags.to_pattern(sb);
				sb.append(code);
				sb.append_c(')');
			}
			else if (code[0]=='^' && code[len-1]!='$')
			{	sb.append("^(?");
				flags.to_pattern(sb);
				sb.append(code[1 : len]);
				sb.append_c(')');
			}
			else if (code[0]!='^' && code[len-1]=='$')
			{	sb.append("(?");
				flags.to_pattern(sb);
				sb.append(code[0 : len-1]);
				sb.append(")$");
			}
			else
			{	sb.append("^(?");
				flags.to_pattern(sb);
				sb.append(code[1 : len-1]);
				sb.append(")$");
			}
		}
		else
		{	int paren_level = 0;
			int from_level = -1;
			bool prev_slash = false;
			bool inside_qt = false;
			Flags flags_stack[1024]; // must be >= sizeof(flags_stack) in Pattern.from_arg(), so no need to check length before access
			flags_stack[0] = flags;
			for (int i=0; tcode[i]!='\0'; i++)
			{	if (i >= offset_from)
				{	if (i >= offset_to)
					{	break;
					}
					if (from_level == -1)
					{	from_level = paren_level;
					}
					if (sb.len == 0)
					{	sb.append("(?");
						flags_stack[paren_level].to_pattern(sb);
					}
					sb.append_c(tcode[i]);
				}
				if (inside_qt)
				{	if (tcode[i] == '"') inside_qt = false;
				}
				else if (prev_slash)
				{	prev_slash = false;
				}
				else
				{	if (tcode[i] == '(')
					{	paren_level++;
						flags_stack[paren_level] = flags_stack[paren_level - 1];
						if (tcode[i+1] == '?')
						{	var j = i + 2;
							while (tcode[j] != ':')
							{	assert(tcode[j]=='-' || tcode[j].isalpha()); // Pattern.from_arg() passes only valid patterns
								if (tcode[j] != '-')
								{	flags_stack[paren_level].apply(tcode, tcode[j], tcode[j-1]!='-');
								}
								j++;
							}
							assert(tcode[j] == ':');
						}
					}
					else if (tcode[i] == ')')
					{	paren_level--;
					}
					else if (tcode[i] == '\\')
					{	prev_slash = true;
					}
					else if (tcode[i] == '"')
					{	inside_qt = true;
					}
				}
			}
			if (from_level != -1)
			{	while (paren_level > from_level)
				{	sb.append_c(')');
					paren_level--;
				}
				while (paren_level < from_level)
				{	sb.prepend_c('(');
					paren_level++;
				}
			}
			if (sb.len == 0)
			{	sb.append_c('(');
			}
			sb.append_c(')');
		}
		return sb.str;
	}

	private Pattern add_reference(string name, bool is_owned, int ref_offset_from, bool meaningful_chars, Flags flags, StringBuilder top_code)
	{	Pattern p;
		int prev_end;
		if (references.length == 0)
		{	// part of pattern before first reference, e.g. /before(?<name>b)//
			prev_end = offset_from;
		}
		else
		{	// part of pattern between references, e.g. /(?<name>a)between(?<name>b)//
			prev_end = references[references.length-1].offset_to;
		}
		if (meaningful_chars)
		{	p = new Pattern(this);
			p.offset_from = prev_end;
			p.offset_to = ref_offset_from;
			p.flags = flags;
			references += p;
		}
		p = new Pattern(this);
		p.name = name;
		p.is_owned = is_owned;
		p.alloc_var_id(); // variable for current reference
		p.offset_from = ref_offset_from; // offset_to must set later
		p.flags = flags;
		references += p;
		return p;
	}

	private void add_reference_done(bool meaningful_chars, StringBuilder top_code)
	{	// after last reference, e.g. /(?<name>a)after//
		if (references.length > 0)
		{	var ref_offset_from = offset_to;
			var prev_end = references[references.length-1].offset_to;
			if (meaningful_chars)
			{	var p = new Pattern(this);
				p.offset_from = prev_end;
				p.offset_to = ref_offset_from;
				p.flags = flags;
				references += p;
			}
		}
	}

	public Pattern.from_arg(string? init_start_conditions, string source_name, string arg, ref int pos, char delim, Flags init_flags, int line=0, int line_char=0) throws Exception
	{	this(null, source_name, line, line_char);
		start_conditions = init_start_conditions;
		flags = init_flags;
		if (delim == '(')
		{	flags.apply(arg, 'x');
		}
		var sb = new StringBuilder();
		int inside = 0; // inside [this] or "this"
		int paren_level = 0;
		Flags flags_stack[1024];
		int ref_from[1024]; // e.g. ref_from[2]==19 means /abc(def(?<ref_name>ghi))//, where "ghi" is at position 19
		flags_stack[0] = flags;
		ref_from[0] = -1;
		unowned Pattern reference_base = this;
		bool meaningful_chars = false;
		bool meaningful_chars_ever = false;
		var from = delim=='(' ? pos-1 : pos;
		var terminator = delim=='(' ? '\0' : delim;
		int i;
		for (i=from; arg[i]!='\0' && arg[i]!=terminator; i++)
		{	if (flags_stack[paren_level].b && arg[i]=='`')
			{	sb.append_c('\'');
				meaningful_chars = true;
				meaningful_chars_ever = true;
				continue;
			}
			if (arg[i] == '\\')
			{	sb.append_c('\\');
				if (arg[i+1] != '\0') i++;
				sb.append_c(arg[i]);
				meaningful_chars = true;
				meaningful_chars_ever = true;
				continue;
			}
			if (inside == 0)
			{	if (arg[i] == '"')
				{	inside = '"';
				}
				else if (arg[i] == '[')
				{	inside = ']';
				}
				else if (arg[i] == '(')
				{	paren_level++;
					if (paren_level == flags_stack.length)
					{	throw_e("Parentheses nesting level is too deep", source_name, arg, i);
					}
					flags_stack[paren_level] = flags_stack[paren_level - 1];
					ref_from[paren_level] = -1;
					if (arg[i+1] == '?')
					{	if (arg[i+2] == '<') // (?<refname>pattern)
						{	i += 3;
							var name_from = i;
							while (arg[i].isalnum() || arg[i]=='_') i++;
							var this_is_owned = false;
							if (i-name_from==5 && arg[name_from : i]=="owned")
							{	this_is_owned = true;
								while (arg[i].isspace()) i++;
								name_from = i;
								while (arg[i].isalnum() || arg[i]=='_') i++;
							}
							if (i-name_from==0 || arg[name_from].isdigit())
							{	throw_e("Expected backreference name like (?<name>pattern)", source_name, arg, i);
							}
							ref_from[paren_level] = (int)sb.len;
							var new_reference_base = reference_base.add_reference(arg[name_from : i], this_is_owned, (int)sb.len, meaningful_chars, flags_stack[paren_level], sb);
							reference_base = new_reference_base;
							if (arg[i] != '>')
							{	throw_e(@"Expected '>' after: $(arg[from : i])", source_name, arg, i, false);
							}
							if (arg[i+1] == '?') // anti (?<name>?x:)
							{	throw_e(@"Unexpected '?' after: $(arg[from : i+1])", source_name, arg, i, false);
							}
							sb.append_c('(');
							continue;
						}
						else
						{	sb.append_c(arg[i++]); // '('
							sb.append_c(arg[i++]); // '?'
							if (arg[i] == '#') // (?# comment )
							{	sb.append_c('#');
								for (i++; arg[i]!='\0'; i++)
								{	if (arg[i] == ')')
									{	i--;
										break;
									}
									sb.append_c(arg[i]);
								}
								continue;
							}
							else
							{	while (arg[i].isalpha() || arg[i]=='-')
								{	var is_positive = arg[i-1] != '-';
									if (arg[i] != '-')
									{	flags_stack[paren_level].apply(arg, arg[i], is_positive);
									}
									sb.append_c(arg[i++]);
								}
								if (arg[i] != ':')
								{	throw_e(@"Expected ':' after: $(arg[from : i])", source_name, arg, i, false);
								}
							}
						}
					}
				}
				else if (arg[i] == ')')
				{	if (paren_level == 0)
					{	throw_e(@"Unexpected ')' after: $(arg[from : i])", source_name, arg, i, false);
					}
					sb.append_c(')');
					var code_from = ref_from[paren_level];
					if (code_from != -1)
					{	assert(reference_base == reference_base.parent.references[reference_base.parent.references.length - 1]);
						reference_base.offset_to = (int)sb.len;
						reference_base.add_reference_done(meaningful_chars, sb);
						reference_base = reference_base.parent;
						meaningful_chars = false; // start finding meaningful chars
					}
					paren_level--;
					if (paren_level==0 && delim=='(')
					{	break;
					}
					continue;
				}
				else if (!flags_stack[paren_level].x && arg[i].isspace())
				{	sb.append_c('\\');
					sb.append_c(arg[i]=='\t' ? 't' : arg[i]=='\r' ? 'r' : arg[i]=='\n' ? 'n' : ' ');
					meaningful_chars = true;
					meaningful_chars_ever = true;
					continue;
				}
				else if (flags_stack[paren_level].x && arg[i]=='/' && arg[i+1]=='*')
				{	sb.append("/*");
					for (i+=2; arg[i]!='\0'; i++)
					{	sb.append_c(arg[i]);
						if (arg[i]=='*' && arg[i+1]=='/')
						{	sb.append_c('/');
							i++;
							break;
						}
						if (arg[i]=='/' && arg[i+1]=='*')
						{	throw_e("\"/*\" and \"*/\" are not allowed in comments", source_name, arg, i);
						}
					}
					continue;
				}
				else if (arg[i]=='/' && paren_level==(delim=='(' ? 1 : 0)) // trailing context operator
				{	var p = paren_level;
					while (p >= 0)
					{	sb.append_c(')');
						p--;
					}
					sb.append_c('/');
					while (p < paren_level)
					{	p++;
						sb.append("(?");
						flags_stack[p].to_pattern(sb);
					}
					continue;
				}
				else if (!flags_stack[paren_level].x || !arg[i].isspace())
				{	meaningful_chars = true;
					meaningful_chars_ever = true;
				}
			}
			else if ((int)(uchar)(arg[i]) == inside)
			{	inside = 0;
			}
			else
			{	meaningful_chars = true;
				meaningful_chars_ever = true;
			}
			sb.append_c(arg[i]);
		}
		if (arg[i] == '\0')
		{	throw_e("Unexpected end of pattern", source_name, arg, from);
		}
		if (paren_level > 0)
		{	throw_e("Parentheses not closed", source_name, arg, i);
		}
		// done
		is_eof_rule = !meaningful_chars_ever;
		code = sb.str;
		offset_to = (int)sb.len;
		add_reference_done(meaningful_chars, sb);
		pos = i;
	}

	private void get_named_refs(ref Pattern[] refs, Pattern? except=null)
	{	if (name!=null && this!=except)
		{	refs.resize(refs.length + 1);
			refs[refs.length - 1] = this;
		}
		for (int i=0; i<references.length; i++)
		{	references[i].get_named_refs(ref refs, except);
		}
	}

	public void to_output_flex(FileStream output_flex, bool with_catch, bool with_catch_write, string all_start_conditions, int global_eof_pattern_id, string eof_echoes) throws Exception
	{	assert(code != null); // is toplevel
		to_output_flex_sub(output_flex, with_catch, with_catch_write, all_start_conditions, global_eof_pattern_id, eof_echoes, true);
		output_flex.putc('\n');
	}

	private void to_output_flex_sub(FileStream output_flex, bool with_catch, bool with_catch_write, string all_start_conditions, int global_eof_pattern_id, string eof_echoes, bool as_top, int inside_state=-1, int next_state=-1, bool do_action=true) throws Exception
	{	if (inside_state == -1)
		{	if (references.length==1 && name==null) // the whole pattern is named
			{	assert(references[0].name != null);
				references[0].to_output_flex_sub
				(	output_flex,
					with_catch,
					with_catch_write,
					all_start_conditions,
					global_eof_pattern_id,
					eof_echoes,
					as_top
				);
				return;
			}
			output_flex.printf("%s\t\t{\n", get_code(all_start_conditions, as_top));
			if (top.is_eof_rule)
			{	if (top.start_conditions == null) // default (global) eof rule
				{	output_flex.puts(eof_echoes);
				}
			}
			else if (top.flags.w)
			{	if (!with_catch_write)
				{	output_flex.puts("\tvsed_write(yytext, yyleng);\n");
				}
				else
				{	output_flex.puts("\tvsed_write(yytext, yyleng, &vsed_error);\n\tVSED_HANDLE_ERROR;\n");
				}
			}
		}
		else if (!can_match_empty())
		{	output_flex.printf("<VSED_SCANNER_S%d>%s\t\t{\n\tvsed_start_stack_pop(1);\n", inside_state, get_code(all_start_conditions, as_top));
		}
		else
		{	output_flex.printf("<VSED_SCANNER_S%d>%s\t\t{\n\tVSED_SCANNER_L%d:\n\tvsed_start_stack_pop(1);\n", inside_state, get_code(all_start_conditions, as_top), inside_state);
		}
		if (references.length == 0)
		{	if (do_action)
			{	var refs = new Pattern[0];
				top.get_named_refs(ref refs, this);
				if (top.is_eof_rule)
				{	output_flex.puts("\tYY_BUFFER_STATE prev_buffer;\n\tprev_buffer = YY_CURRENT_BUFFER;\n");
				}
				output_flex.printf("\tvsed_a%d", top.id);
				char comma = '(';
				for (int i=0; i<refs.length; i++)
				{	output_flex.printf("%cvsed_text_%d,vsed_len_%d", comma, refs[i].var_id, refs[i].var_id);
					comma = ',';
				}
				var vsed_error = with_catch ? ",&vsed_error" : "";
				if (top.is_eof_rule)
				{	output_flex.printf("%c\"\",0%s);\n", comma, vsed_error);
				}
				else
				{	if (!is_owned)
					{	output_flex.printf("%cyytext,yyleng", comma);
					}
					else
					{	output_flex.printf("%cg_memdup(yytext, yyleng+1),yyleng", comma);
					}
					output_flex.printf("%s); /* %s */\n", vsed_error, name!=null?name:"text");
				}
				for (int i=0; i<refs.length; i++)
				{	if (!refs[i].is_owned)
					{	output_flex.printf("\tif (vsed_len_%d != 0) g_free(vsed_text_%d);\n", refs[i].var_id, refs[i].var_id);
					}
				}
				if (with_catch)
				{	output_flex.puts("\tVSED_HANDLE_ERROR;\n");
				}
				if (top.is_eof_rule)
				{	output_flex.puts("\tif (YY_CURRENT_BUFFER==NULL || YY_CURRENT_BUFFER==prev_buffer)");
					if (top.start_conditions==null || global_eof_pattern_id==-1)
					{	output_flex.puts(" return 0;\n");
					}
					else
					{	output_flex.printf("\n\t{\tvsed_a%d(\"\",0%s); /* try default eof action */\n", global_eof_pattern_id, vsed_error);
						if (with_catch)
						{	output_flex.puts("\t\tVSED_HANDLE_ERROR;\n");
						}
						output_flex.puts("\t\tif (YY_CURRENT_BUFFER==NULL || YY_CURRENT_BUFFER==prev_buffer) return 0;\n\t}\n");
					}
				}
				if (next_state != -1)
				{	output_flex.puts("\tvsed_regex_len -= yyleng;\n\twhile (vsed_regex_len-- > 0) input();\n");
				}
			}
			else
			{	assert(parent != null);
				assert(next_state != -1); // for last state is called with do_action
				output_flex.printf("\tvsed_trace_meta(%d);\n\tvsed_regex_len -= yyleng;\n\t", id);
				if (can_match_whole_here())
				{	output_empty_next_vars(output_flex, with_catch);
					if (name != null)
					{	output_flex_alloc(output_flex, "\t\t");
						output_flex.puts("\t\t");
					}
					output_flex.printf("vsed_start_stack_push(VSED_SCANNER_S%d);\n\t}\n", next_state);
				}
				else
				{	if (name != null)
					{	output_flex_alloc(output_flex, "\t");
						output_flex.puts("\t");
					}
					output_flex.printf("vsed_start_stack_push(VSED_SCANNER_S%d);\n", next_state);
				}
			}
			output_flex.puts("}\n");
		}
		else
		{	if (name != null)
			{	output_flex.putc('\t');
				output_flex_alloc(output_flex, "\t");
			}
			output_flex.printf
			(	"\tvsed_trace_action(\"%s\", %d, %d);\n\tvsed_regex_len = yyleng;\n\t",
				to_c_string(top.source_name),
				line,
				line_char
			);
			var can = can_match_empty();
			if (can)
			{	references[0].output_empty_next_vars(output_flex, with_catch);
			}
			output_flex.printf("yyless(0);\n\tvsed_start_stack_push(VSED_SCANNER_S%d);\n", references[0].id);
			if (!can)
			{	output_flex.puts("}\n");
			}
			else
			{	output_flex.puts("\t}\n}\n");
			}
			for (int i=0; i<references.length; i++)
			{	var cur_do_action = references[i].var_id==top.max_var_id;
				references[i].to_output_flex_sub
				(	output_flex,
					with_catch,
					with_catch_write,
					all_start_conditions,
					global_eof_pattern_id,
					eof_echoes,
					false,
					references[i].id,
					i+1==references.length ? next_state : references[i+1].id,
					cur_do_action
				);
				if (cur_do_action) break;
			}
		}
		if (inside_state!=-1 && can_match_empty())
		{	output_flex.printf
			(	"<VSED_SCANNER_S%d>(?s:.)\t\t{\n\tyyless(0);\n\tgoto VSED_SCANNER_L%d;\n}\n",
				inside_state, inside_state
			);
		}
	}

	private void output_flex_alloc(FileStream output_flex, string indent)
	{	assert(name != null);
		output_flex.printf("vsed_text_%d = ", var_id);
		if (!is_owned)
		{	output_flex.puts("yyleng==0 ? \"\" : ");
		}
		output_flex.printf("g_memdup(yytext, yyleng+1); /* %s */\n%svsed_len_%d = yyleng;\n", name, indent, var_id);
	}

	/**	Whether this pattern can match empty string. E.g. this can: [a-z]*. This cannot: [a-z]+.
	 **/
	private bool can_match_empty()
	{	return true; // TODO: implement to eliminate unnecessary code in flex file
	}

	private bool can_match_whole_here()
	{	bool is_after = false;
		for (int i=0; i<parent.references.length; i++)
		{	unowned Pattern pat = parent.references[i];
			if (!is_after)
			{	if (pat == this) is_after = true;
			}
			else
			{	if (!pat.can_match_empty()) return false;
			}
		}
		return true;
	}

	private void output_empty_next_vars(FileStream output_flex, bool with_catch)
	{	output_flex.printf("if (vsed_regex_len == 0)\n\t{\tvsed_a%d(", top.id);
		bool is_after = false;
		bool comma = false;
		string free = "";
		output_empty_next_vars_sub(output_flex, with_catch, parent, ref is_after, ref comma, ref free);
		if (!comma)
		{	output_flex.puts("\"\", 0"); // default capture (text and text_len)
		}
		if (!with_catch)
		{	output_flex.printf(");%s", free);
		}
		else
		{	output_flex.printf(", &vsed_error);%s\n\t\tVSED_HANDLE_ERROR;", free);
		}
		output_flex.puts("\n\t}\n\telse\n\t{\t");
	}

	private void output_empty_next_vars_sub(FileStream output_flex, bool with_catch, Pattern subj, ref bool is_after, ref bool comma, ref string free)
	{	if (subj == this)
		{	is_after = true;
		}
		if (subj.name != null)
		{	if (comma)
			{	output_flex.puts(", ");
			}
			comma = true;
			if (subj == this)
			{	if (!subj.is_owned)
				{	output_flex.puts("yytext, yyleng");
				}
				else
				{	output_flex.puts("g_memdup(yytext, yyleng+1), yyleng");
				}
			}
			else if (!is_after && subj.var_id!=top.max_var_id) // max var_id is unused
			{	output_flex.printf("vsed_text_%d, vsed_len_%d", subj.var_id, subj.var_id);
				if (!subj.is_owned)
				{	free += "\n\t\tif (vsed_len_%d != 0) g_free(vsed_text_%d);".printf(subj.var_id, subj.var_id);
				}
			}
			else
			{	if (!subj.is_owned)
				{	output_flex.puts("\"\", 0");
				}
				else
				{	output_flex.puts("g_malloc0(1), 0");
				}
			}
		}
		for (int i=0; i<subj.references.length; i++)
		{	output_empty_next_vars_sub(output_flex, with_catch, subj.references[i], ref is_after, ref comma, ref free);
		}
	}

	public string get_c_prototype(bool with_catch)
	{	if (references.length == 0)
		{	if (!with_catch)
			{	return @"\tinline void vsed_a$id(const gchar*, int);\n";
			}
			else
			{	return @"\tinline void vsed_a$id(const gchar*, int, GError**);\n";
			}
		}
		else
		{	var sb = new StringBuilder();
			sb.append_printf("\tinline void vsed_a%d(", id);
			var refs = new Pattern[0];
			get_named_refs(ref refs);
			for (int i=0; i<refs.length; i++)
			{	if (i > 0)
				{	sb.append(", ");
				}
				if (!refs[i].is_owned)
				{	sb.append("const ");
				}
				sb.append("gchar*, gint");
			}
			if (with_catch)
			{	sb.append(", GError**");
			}
			sb.append(");\n");
			return sb.str;
		}
	}

	public string get_vala_prototype(bool with_catch)
	{	if (references.length == 0)
		{	if (!with_catch)
			{	return @"public inline void a$id(string text, int text_len)";
			}
			else
			{	return @"public inline void a$id(string text, int text_len) throws GLib.Error";
			}
		}
		else
		{	var sb = new StringBuilder();
			sb.append_printf("public inline void a%d(", id);
			var refs = new Pattern[0];
			get_named_refs(ref refs);
			for (int i=0; i<refs.length; i++)
			{	if (i > 0) sb.append(", ");
				if (refs[i].is_owned)
				{	sb.append("owned ");
				}
				sb.append_printf("string %s, int %s_len", refs[i].name, refs[i].name);
			}
			sb.append(")");
			if (with_catch)
			{	sb.append(" throws GLib.Error");
			}
			return sb.str;
		}
	}

	public static void to_output_flex_global(FileStream output_flex)
	{	if (max_var_id_overall > 1) // last var is never used (not assigned like vsed_text_1 = g_memdup(yytext, yyleng+1)), instead yytext is used directly
		{	output_flex.puts("%{\n");
			for (int i=1; i<max_var_id_overall; i++)
			{	output_flex.printf("\tchar *vsed_text_%d;\n", i);
				output_flex.printf("\tint vsed_len_%d;\n", i);
			}
			output_flex.puts("%}\n\n");
		}
		if (enum_ids > 0)
		{	output_flex.puts("%x");
			for (int i=1; i<=enum_ids; i++)
			{	output_flex.printf(" VSED_SCANNER_S%d", i);
			}
			output_flex.puts("\n\n");
		}
	}
}

string to_c_string(string str)
{	return str.replace("\\", "\\\\").replace("\"", "\\\"").replace("\r", "\\r").replace("\n", "\\n");
}

int normal_skip(string? source_name, string str, int i, string to_chars, bool is_vala, out bool no_eof) throws Exception
{	no_eof = true;
	char level[4096];
	int level_pos = 0;
	for (; str[i]!='\0'; i++)
	{	if (level_pos == 0)
		{	for (int j=0; to_chars[j]!='\0'; j++)
			{	if (str[i] == to_chars[j]) return i+1;
			}
		}
		else if (str[i]==')' || str[i]==']' || str[i]=='}')
		{	if (str[i] == level[level_pos-1])
			{	level_pos--;
				continue;
			}
			if (source_name != null)
			{	throw_e("Expected '%c', got '%c'".printf(level[level_pos-1], str[i]), source_name, str, i);
			}
		}
		if (str[i] == '(')
		{	level[level_pos++] = ')';
		}
		else if (str[i] == '[')
		{	level[level_pos++] = ']';
		}
		else if (str[i] == '{')
		{	level[level_pos++] = '}';
		}
		else if (str[i]=='\\' && str[i+1]!='\0')
		{	i++;
		}
		else if (str[i]=='"' || is_vala && (str[i]=='\'' || str[i]=='`'))
		{	var qt = str[i];
			if (is_vala && qt=='"' && str[i+1]=='"' && str[i+2]=='"')
			{	int n_qt = 0;
				for (i+=3; str[i]!='\0'; i++)
				{	if (str[i] == '"')
					{	if (++n_qt == 3) break;
					}
					else
					{	n_qt = 0;
					}
				}
				if (str[i] == '\0') i--;
			}
			else
			{	while (true)
				{	i++;
					if (str[i] == qt) break;
					if (is_vala && source_name!=null && (str[i]=='\r' || str[i]=='\n'))
					{	throw_e("Broken quotes: unexpected newline", source_name, str, i);
					}
					if (str[i] == '\\') i++;
					if (str[i] == '\0')
					{	i--;
						break;
					}
				}
			}
		}
		else if (str[i]=='/' && str[i+1]=='*')
		{	i += 2;
			if (!is_vala || source_name==null)
			{	while (str[i]!='\0' && !(str[i]=='*' && str[i+1]=='/')) i++;
			}
			else
			{	while (str[i]!='\0' && !(str[i]=='*' && str[i+1]=='/'))
				{	if (str[i]=='/' && str[i+1]=='*')
					{	throw_e("\"/*\" and \"*/\" are not allowed in comments", source_name, str, i);
					}
					i++;
				}
			}
			if (str[i] != '\0')
			{	assert(str[i]=='*' && str[i+1]=='/');
				i++;
			}
			else
			{	if (is_vala && source_name!=null)
				{	throw_e("Comment not terminated (\"*/\" expected)", source_name, str, i);
				}
				i--;
			}
		}
		else if (is_vala && str[i]=='/' && str[i+1]=='/')
		{	i += 2;
			while (str[i]!='\0' && str[i]!='\r' && str[i]!='\n') i++;
			i--;
		}
	}
	no_eof = false;
	return i;
}

class Expr
{	public Pattern? pattern;
	private string action;
	private Flags flags;
	private string? source_name;
	private int line;
	private int line_char;
	private string? chained_state = null;

	public Expr.p(Pattern pattern, string action, Flags flags, string? source_name=null, int line=0, int line_char=0)
	{	this.pattern = pattern;
		this.action = action;
		this.flags = flags;
		this.source_name = source_name;
		this.line = line;
		this.line_char = line_char;
	}

	public Expr(string action, Flags flags, string? source_name=null, int line=0, int line_char=0)
	{	this.pattern = null;
		this.action = action;
		this.flags = flags;
		this.source_name = source_name;
		this.line = line;
		this.line_char = line_char;
	}

	public void chain_state(string state)
	{	assert(chained_state == null);
		chained_state = state;
	}

	public string to_vala_string(bool ensure_semicolon=false, bool respect_w_flag=false)
	{	unowned string str = action;
		var sb = new StringBuilder();
		if (source_name != null)
		{	sb.append_printf("\t\ttrace_action(\"%s\", %d, %d);\n", to_c_string(source_name), line, line_char);
		}
		if (flags.w && respect_w_flag)
		{	sb.append("\t\twrite(text, text_len);\n");
		}
		if (flags.e)
		{	sb.append(str);
			if (ensure_semicolon)
			{	sb.append("\n\t\t;"); // \n to terminate line comment
			}
		}
		else
		{	var inject_pos = sb.len;
			size_t inside_from = -1;
			for (int i=0; str[i]!='\0'; i++)
			{	if (str[i] == '\\')
				{	if (inside_from == -1)
					{	sb.append("write(\"");
						inside_from = sb.len;
					}
					if (str[i+1] == '`')
					{	sb.append_c('`');
						i++;
						continue;
					}
					sb.append_c('\\');
					if (str[i+1] != '\0')
					{	i++;
					}
				}
				else if (str[i] == '"')
				{	if (inside_from == -1)
					{	sb.append("write(\"");
						inside_from = sb.len;
					}
					sb.append_c('\\'); // output \ before outputting " (see below)
				}
				else if (str[i] == '$')
				{	var dollar_at = i;
					i++;
					var br = '\0';
					if (str[i] == '{')
					{	i++;
						br = '}';
					}
					else if (str[i] == '(')
					{	i++;
						br = ')';
					}
					var from = i;
					while (str[i].isalnum() || str[i]=='_')
					{	i++;
					}
					var to = i;
					var ok = from!=to && !str[from].isdigit();
					if (br != '\0')
					{	char br_str[2];
						br_str[0] = br;
						br_str[1] = '\0';
						try
						{	i = normal_skip(null, str, i, (string)br_str, true, out ok);
						}
						catch
						{	GLib.assert_not_reached();
						}
						to = i - 1;
					}
					if (!ok)
					{	i = dollar_at;
					}
					else
					{	if (inside_from != -1)
						{	sb.append_printf("\", %d);\n", (int)(sb.len-inside_from));
							inside_from = -1;
						}
						if (inject_pos != -1)
						{	sb.insert(inject_pos, "string? s = null;\n\t\t");
							inject_pos = -1;
						}
						sb.append_printf("s = @\"$(%s)\";\n\t\twrite(s, s.length);\n\t\t", str[from : to]);
						i--;
						continue;
					}
				}
				if (inside_from == -1)
				{	sb.append("write(\"");
					inside_from = sb.len;
				}
				sb.append_c(str[i]);
			}
			if (inside_from != -1)
			{	sb.append_printf("\", %d);\n\t\t", (int)(sb.len-inside_from));
			}
		}
		if (chained_state != null)
		{	sb.append_printf("push(S.%s);", chained_state);
		}
		return sb.str;
	}
}

class Eofs
{	private string[] eofs_start_conditions = {};
	public int global_eof_pattern_id = -1;

	public void add(string[] start_conditions, Pattern pattern, string source_name, string arg, int i) throws Exception
	{	if (start_conditions.length == 0)
		{	if (global_eof_pattern_id != -1)
			{	throw_e("Default end-of-stream pattern is already specified", source_name, arg, i);
			}
			global_eof_pattern_id = pattern.get_id();
		}
		else
		{	for (int j=0; j<start_conditions.length; j++)
			{	for (int k=0; k<eofs_start_conditions.length; k++)
				{	if (start_conditions[j] == eofs_start_conditions[k])
					{	throw_e(@"End-of-stream action for state $(start_conditions[j]) is already defined", source_name, arg, i);
					}
				}
				eofs_start_conditions += start_conditions[j];
			}
		}
	}

	public bool has_for_start_condition(string start_condition)
	{	for (int i=0; i<eofs_start_conditions.length; i++)
		{	if (eofs_start_conditions[i] == start_condition) return true;
		}
		return false;
	}
}

class ExprInState
{	public string start_condition;
	public Expr expr;

	public ExprInState(string start_condition, Expr expr)
	{	this.start_condition = start_condition;
		this.expr = expr;
	}
}

abstract class ExprsInState
{	protected Expr? default_expr = null;
	protected ExprInState[] exprs = {};

	protected abstract string action_name();

	public void add(string[] start_conditions, Expr expr, string source_name, string arg, int i) throws Exception
	{	if (start_conditions.length == 0)
		{	if (default_expr != null)
			{	throw_e(@"Default $(action_name()) action is already defined", source_name, arg, i);
			}
			default_expr = expr;
		}
		else
		{	for (int j=0; j<start_conditions.length; j++)
			{	for (int k=0; k<exprs.length; k++)
				{	if (start_conditions[j] == exprs[k].start_condition)
					{	throw_e(@"$(action_name()) action for state $(start_conditions[j]) is already defined", source_name, arg, i);
					}
				}
				exprs += new ExprInState(start_conditions[j], expr);
			}
		}
	}

	public int length()
	{	return exprs.length + (default_expr==null ? 0 : 1);
	}

	public string to_vala_string()
	{	var sb = new StringBuilder();
		for (int i=0; i<exprs.length; i++)
		{	sb.append("\t\t");
			if (i > 0) sb.append("else ");
			sb.append_printf("if (state == S.%s)\n\t\t{\n%s\n\t\t}\n", exprs[i].start_condition, exprs[i].expr.to_vala_string(true, true));
		}
		if (default_expr == null)
		{	default_expr = new Expr("", Flags("w"));
		}
		if (exprs.length == 0)
		{	sb.append(default_expr.to_vala_string(true, true));
		}
		else
		{	sb.append_printf("\t\telse\n\t\t{\n\t\t\t%s\n\t\t}\n", default_expr.to_vala_string(true, true));
		}
		return sb.str;
	}
}

class Echoes : ExprsInState
{	protected override string action_name()
	{	return "Echo";
	}

	public string get_eof_echoes(Eofs eofs, bool with_catch)
	{	var sb = new StringBuilder();
		for (int i=0; i<exprs.length; i++)
		{	var state = exprs[i].start_condition;
			if (!eofs.has_for_start_condition(state))
			{	if (sb.len == 0)
				{	sb.append("\t/* Call echoes with empty text, to give chance to pop() */\n\twhile (YY_START==");
				}
				else
				{	sb.append(" || YY_START==");
				}
				sb.append(state);
			}
		}
		if (sb.len == 0)
		{	return "";
		}
		sb.append_printf(")\n\t{\tint prev_level = vsed_start_stack_len;\n\t\tvsed_echo(\"\",0");
		if (with_catch)
		{	sb.append(",&vsed_error);\n\t\tVSED_HANDLE_ERROR;\n");
		}
		else
		{	sb.append(");\n");
		}
		sb.append("\t\tif (vsed_start_stack_len >= prev_level) break; /* if not popped */\n\t}\n");
		return sb.str;
	}
}

class Catches : ExprsInState
{	protected override string action_name()
	{	return "Catch";
	}

	public string to_vala_string_default()
	{	if (default_expr != null)
		{	return default_expr.to_vala_string(true, false);
		}
		return "exit(1, \"%s\", error.message);";
	}

	public string to_vala_string_catches()
	{	var sb = new StringBuilder();
		for (int i=0; i<exprs.length; i++)
		{	sb.append_printf("\tpublic inline void catch_%s(GLib.Error error) throws GLib.Error\n\t{\t%s\n\t}\n", exprs[i].start_condition, exprs[i].expr.to_vala_string(true, false));
		}
		return sb.str;
	}

	public string to_catch_intermed_string()
	{	if (exprs.length == 0)
		{	return "";
		}
		var sb = new StringBuilder();
		for (int i=0; i<exprs.length; i++)
		{	sb.append_printf("inline void vsed_catch_%s(GError*, GError**);\n", exprs[i].start_condition);
		}
		sb.append("void vsed_catch_intermed()\n{\tGError *tmp_error = NULL;\n\twhile (vsed_start_stack_len > 0)\n\t{\t");
		for (int i=0; i<exprs.length; i++)
		{	sb.append_printf("if (YY_START == %s)\n\t\t{\tvsed_catch_%s(vsed_error, &tmp_error);\n\t\t}\n\t\t", exprs[i].start_condition, exprs[i].start_condition);
		}
		sb.append("else\n\t\t{\tvsed_start_stack_pop(1);\n\t\t\tcontinue;\n\t\t}\n\t\tg_error_free(vsed_error);\n\t\tvsed_error = tmp_error;\n\t\tbreak;\n\t}\n}\n");
		return sb.str;
	}

	public bool has_intermed()
	{	return exprs.length > 0;
	}
}

class Result
{	private Expr[] v_blocks_global = {};
	private Expr[] v_blocks_var_init = {};
	private Expr[] exprs = {};
	private Echoes echoes = new Echoes();
	private Catches catches = new Catches();
	private Expr? init_block = null;
	private Expr? final_block = null;
	private Expr? puts_block = null;
	private Expr? putc_block = null;
	private Expr? printf_block = null;
	private Expr? write_block = null;
	private string[] start_conditions = new string[0];
	private Eofs eofs = new Eofs();
	private StringBuilder defs = new StringBuilder();
	private string[] pkgs = {};
	private string[] uses = {};
	private string valac_args_user = "-X -O2";
	private string flex_options = "";
	private bool flex_options_default = true;
	private bool flex_debug_enabled = false;
	private bool is_verbose = false;
	public string? compile = null;

	public bool with_catch_puts()
	{	return catches.length()>0 && puts_block!=null;
	}

	public bool with_catch_putc()
	{	return catches.length()>0 && putc_block!=null;
	}

	public bool with_catch_printf()
	{	return catches.length()>0 && printf_block!=null;
	}

	public bool with_catch_write()
	{	return catches.length()>0 && write_block!=null;
	}

	public string get_valac_args()
	{	var sb = new StringBuilder();
		if (catches.length() > 0)
		{	sb.append("-D VSED_WITH_CATCH ");
		}
		if (with_catch_puts())
		{	sb.append("-D VSED_WITH_CATCH_PUTS ");
		}
		if (with_catch_putc())
		{	sb.append("-D VSED_WITH_CATCH_PUTC ");
		}
		if (with_catch_printf())
		{	sb.append("-D VSED_WITH_CATCH_PRINTF ");
		}
		if (with_catch_write())
		{	sb.append("-D VSED_WITH_CATCH_WRITE ");
		}
		sb.append(valac_args_user);
		for (int i=0; i<pkgs.length; i++)
		{	sb.append(" --pkg ");
			sb.append(pkgs[i]);
		}
		for (int i=0; i<uses.length; i++)
		{	sb.append_c(' ');
			sb.append(uses[i]);
		}
		return sb.str;
	}

	private string strarr_join(string? separ, string[] arr)
	{	if (arr.length == 0)
		{	return "";
		}
		int separ_len = separ==null ? 0 : separ.length;
		int len = arr[0].length;
		for (int i=1; i<arr.length; i++)
		{	len += separ_len + arr[i].length;
		}
		var sb = new StringBuilder.sized(len+1);
		sb.append(arr[0]);
		for (int i=1; i<arr.length; i++)
		{	if (separ != null) sb.append(separ);
			sb.append(arr[i]);
		}
		return sb.str;
	}

	public string get_pkgs()
	{	return strarr_join(" ", pkgs);
	}

	public Result(string[] args) throws Exception
	{	for (int a=1; a<args.length; a++)
		{	parse_arg(args[a], "\"argument %d\"".printf(a), 0);
		}
	}

	private void parse_arg(string arg, string source_name, int call_level) throws Exception
	{	int i = 0;
		if (arg[0]=='\xEF' && arg[1]=='\xBB' && arg[2]=='\xBF') // utf-8 BOM
		{	i = 3;
		}
		i = parse_arg_sub(arg, i, source_name, call_level, false);
		assert(arg[i] == '\0');
	}

	private int parse_arg_sub(string arg, int i, string source_name, int call_level, bool for_block) throws Exception
	{	assert(!for_block || arg[i]=='<');
		string[] in_start_conditions = {};
		string[] in_chained = {};
		string[]? in_chained_cur_1 = null;
		bool[] br = {};
		bool in_start_conditions_block = false;
		while (arg[i] != '\0')
		{	// Skip space
			i = skip_space(source_name, arg, i);
			if (arg[i] == '}')
			{	// Close block
				assert(br.length == in_chained.length);
				if (br.length>0 && br[br.length-1])
				{	br.resize(br.length - 1);
					in_chained.resize(in_chained.length - 1);
					in_chained_cur_1 = br.length==0 ? null : new string[] {in_chained[br.length-1]};
				}
				else if (br.length==0 && in_start_conditions_block)
				{	if (for_block) return i + 1;
					in_start_conditions.resize(0);
					in_start_conditions_block = false;
				}
				else
				{	throw_e("Unexpected '}'", source_name, arg, i);
				}
				i = skip_space(source_name, arg, i+1);
			}
			if (arg[i] == '<')
			{	// <START,CONDITIONS,LIST>
				if (br.length>0 && !br[br.length - 1])
				{	throw_e("Start condition is already specified", source_name, arg, i);
				}
				if (in_start_conditions.length!=0 || br.length>0)
				{	i = parse_arg_sub(arg, i, source_name, call_level, true);
					continue;
				}
				i = read_start_conditions(arg, i, source_name, out in_start_conditions);
				if (arg[i] == '{')
				{	in_start_conditions_block = true;
					i = skip_space(source_name, arg, i+1);
				}
			}
			bool was_skipped;
			Expr? regex_echo_or_catch;
			i = skip_directive
			(	arg,
				i,
				source_name,
				call_level,
				in_chained_cur_1!=null ? in_chained_cur_1 : in_start_conditions,
				out was_skipped,
				out regex_echo_or_catch
			);
			if (br.length > 0)
			{	if (!br[br.length - 1])
				{	if (for_block) return i;
					br.resize(br.length - 1);
					in_chained.resize(in_chained.length - 1);
					in_chained_cur_1 = br.length==0 ? null : new string[] {in_chained[br.length-1]};
				}
			}
			else if (in_start_conditions.length != 0)
			{	if (regex_echo_or_catch == null)
				{	throw_e("Pattern, echo or block expected after <START,CONDITIONS,LIST>", source_name, arg, i);
				}
				assert(was_skipped && regex_echo_or_catch!=null);
				if (!in_start_conditions_block)
				{	if (for_block) return i;
					in_start_conditions.resize(0);
				}
			}
			i = skip_space(source_name, arg, i);
			if (arg[i] == '\0')
			{	break;
			}
			if (!was_skipped)
			{	throw_e("Unsupported command", source_name, arg, i);
			}
			if (arg[i] == ';')
			{	while (arg[i] == ';')
				{	i = skip_space(source_name, arg, i+1);
				}
			}
			else if (arg[i]=='<' && regex_echo_or_catch!=null)
			{	// chained start condition
				i = read_start_conditions(arg, i, source_name, out in_chained_cur_1);
				if (in_chained_cur_1.length!=1 || in_chained_cur_1[0]=="*" || in_chained_cur_1[0].ascii_casecmp("INITIAL")==0)
				{	throw_e("Expected <CHAINED_START_CONDITION>, that must not be \"*\" or \"INITIAL\"", source_name, arg, i);
				}
				in_chained += in_chained_cur_1[0];
				br += arg[i] == '{';
				regex_echo_or_catch.chain_state(in_chained_cur_1[0]);
				if (arg[i] == '{')
				{	i++;
				}
			}
			else if (arg[i] != '}')
			{	throw_e("Expected ';'", source_name, arg, i);
			}
		}
		if (br.length>0 && (br.length>1 || br[0]))
		{	throw_e("Expected '}' at end (<CHAINED_START_CONDITION> block is not closed)", source_name, arg, i);
		}
		if (in_start_conditions_block)
		{	throw_e("Expected '}' at end (<START,CONDITIONS,LIST> block is not closed)", source_name, arg, i);
		}
		return i;
	}

	private int read_start_conditions(string arg, int i, string source_name, out string[] start_conditions) throws Exception
	{	assert(arg[i] == '<');
		start_conditions = {};
		for (bool is_first=true; true; is_first=false)
		{	i = skip_space(source_name, arg, i+1);
			var from = i;
			while (arg[i].isalnum() || arg[i]=='_') i++;
			var to = i;
			i = skip_space(source_name, arg, i);
			if (to==from || !arg[from].isalpha())
			{	if (arg[from]=='*' && is_first)
				{	to++;
					i++;
				}
				else
				{	throw_e("Name of a start condition expected after '<'", source_name, arg, i);
				}
			}
			var c = arg[from : to].up();
			if (c[0] != '*')
			{	add_start_condition(c);
				for (int j=0; j<start_conditions.length; j++)
				{	if (start_conditions[j] == c)
					{	throw_e(@"Double start condition \"$c\"", source_name, arg, i);
					}
				}
				start_conditions.resize(start_conditions.length + 1);
				start_conditions[start_conditions.length - 1] = c;
				if (arg[i] == ',') continue;
			}
			else
			{	start_conditions.resize(start_conditions.length + 1);
				start_conditions[start_conditions.length - 1] = c;
			}
			if (arg[i] != '>')
			{	throw_e("Expected '>' after start conditions list", source_name, arg, i);
			}
			i++;
			break;
		}
		return skip_space(source_name, arg, i);
	}

	private int skip_directive(string arg, int i, string source_name, int call_level, string[] in_start_conditions, out bool was_skipped, out Expr? regex_echo_or_catch) throws Exception
	{	was_skipped = true;
		regex_echo_or_catch = null;
		var directive_at = i;
		int line=-1, line_char=-1;
		if (is_verbose || can_be_delim(arg[i]))
		{	calc_location(arg, i, out line, out line_char);
		}
		if (can_be_delim(arg[i]))
		{	// /pattern/action/flags
			var delim = arg[i++];
			// /pattern/action/
			var pattern_from = i;
			i = skip_pattern_to_delim(source_name, arg, i, delim);
			assert(arg[i-1] == delim);
			string action_str;
			Flags flags;
			i = read_delimited(source_name, arg, i-1, out action_str, out flags, false, false, false);
			// add to this
			string? s = in_start_conditions.length==0 ? null : strarr_join(",", in_start_conditions);
			var pattern = new Pattern.from_arg(s, source_name, arg, ref pattern_from, delim, flags, line, line_char);
			if (pattern.is_eof_rule)
			{	eofs.add(in_start_conditions, pattern, source_name, arg, i);
			}
			regex_echo_or_catch = new Expr.p(pattern, action_str, flags, source_name, line, line_char);
			this.exprs += regex_echo_or_catch;
			if (is_verbose)
			{	stderr.printf("Directive pattern at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='v' && !arg[i+1].isalnum() && arg[i+1]!='_')
		{	// v/type name [= ...]/ OR v/type name(...){...}
			string str;
			Flags flags;
			i = read_delimited(source_name, arg, i+1, out str, out flags, true, true, false);
			bool ok = true;
			int from = 0;
			for (int j=0; ok && str[j]!='\0';)
			{	j = skip_space(source_name, str, j, true);
				from = j;
				var last_word_from = j;
				if (str[j].isalpha())
				{	j++;
					int level = 0;
					int n_idents = 1;
					bool prev_isw = true;
					while (str[j].isalnum() || str[j]=='_' || str[j]=='.' || str[j]==',' || str[j]=='?' || str[j]=='[' || str[j]==']' || str[j]=='<' || str[j]=='>' || str[j].isspace())
					{	if (str[j]=='[' || str[j]=='<')
						{	level++;
						}
						else if (str[j]==']' || str[j]=='>')
						{	level--;
						}
						bool cur_isw = str[j].isalnum() || str[j]=='_';
						if (level==0 && !prev_isw && cur_isw)
						{	last_word_from = j;
							if (n_idents++ > 2) break;
						}
						prev_isw = cur_isw;
						j++;
					}
					if (level != 0)
					{	ok = false;
						break;
					}
				}
				if (j-from == 0)
				{	ok = false;
					break;
				}
				j = skip_space(source_name, str, j, true);
				if (str[j]=='=' || str[j]==';' || str[j]=='\0')
				{	var eq_at = j;
					bool no_eof;
					try
					{	j = normal_skip(null, str, j, ";", true, out no_eof);
					}
					catch
					{	GLib.assert_not_reached();
					}
					var to = no_eof ? j-1 : j;
					var code = str[from : to];
					if (code.has_prefix("using") && !(code[5].isalnum() || code[5]=='_'))
					{	this.v_blocks_global += new Expr(code+";", flags);
					}
					else
					{	this.v_blocks_global += new Expr(str[from : eq_at]+";", flags); // private, so array concatenation will be supported
						if (str[eq_at] == '=')
						{	this.v_blocks_var_init += new Expr(str[last_word_from : to]+";", flags);
						}
					}
				}
				else if (str[j] == '(')
				{	try
					{	j = normal_skip(null, str, j+1, ")", true, out ok);
						j = normal_skip(null, str, j, "{", true, out ok);
						j = normal_skip(null, str, j, "}", true, out ok);
					}
					catch
					{	GLib.assert_not_reached();
					}
					if (ok)
					{	this.v_blocks_global += new Expr(str[from : j], flags);
					}
				}
				else
				{	var code = str[from : j];
					if (code.has_prefix("struct") || code.has_prefix("class") || code.has_prefix("enum") || code.has_prefix("errordomain"))
					{	try
						{	j = normal_skip(null, str, j, "{", true, out ok);
							j = normal_skip(null, str, j, "}", true, out ok);
						}
						catch
						{	GLib.assert_not_reached();
						}
						if (ok)
						{	this.v_blocks_global += new Expr("public "+str[from : j], flags);
						}
					}
					else
					{	ok = false;
					}
				}
			}
			if (!ok)
			{	throw_e(@"Expected v/type name [= ...]/ OR v/type name(...){...}/ OR v/struct|class|enum|errordomain|using name.../: $(str[from : str.length])", source_name, arg, i, false);
			}
			if (is_verbose)
			{	stderr.printf("Directive v at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='v' && arg[i+1]=='c' && !arg[i+2].isalnum() && arg[i+2]!='_')
		{	// vc /.../
			Flags flags;
			i = read_delimited(source_name, arg, i+2, out this.valac_args_user, out flags, true, false, true);
			this.valac_args_user = this.valac_args_user.replace("\r", " ").replace("\n", " ");
			if (is_verbose)
			{	stderr.printf("Directive vc at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='o' && arg[i+1]=='p' && arg[i+2]=='t' && arg[i+3]=='i' && arg[i+4]=='o' && arg[i+5]=='n' && !arg[i+6].isalnum() && arg[i+6]!='_')
		{	// option /.../
			Flags flags;
			string str;
			i = read_delimited(source_name, arg, i+6, out str, out flags, true, false, true);
			var j = skip_space(source_name, str, 0);
			while (str[j] != '\0')
			{	var from = j;
				while (str[j].isalnum() || str[j]=='_' || str[j]=='-') j++;
				var to = j;
				j = skip_space(source_name, str, j);
				if (to>from && str[j]=='=')
				{	j = skip_space(source_name, str, j+1);
					if (str[j] == '"')
					{	j++;
						while (str[j]!='\0' && str[j]!='"') j++;
						if (str[j] == '"') j++;
					}
					else
					{	while (str[j]!='\0' && !str[j].isspace()) j++;
					}
					to = j;
				}
				if (from == to)
				{	throw_e("Option expected", source_name, arg, i);
				}
				var option = str[from : to];
				if (option == "vsed-debug")
				{	flex_debug_enabled = true;
				}
				else
				{	if (option == "verbose")
					{	is_verbose = true;
						calc_location(arg, directive_at, out line, out line_char);
					}
					this.flex_options += " "+option;
				}
			}
			flex_options_default = false;
			if (is_verbose)
			{	stderr.printf("Directive option at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='e' && arg[i+1]=='c' && arg[i+2]=='h' && arg[i+3]=='o' && !arg[i+4].isalnum() && arg[i+4]!='_')
		{	// echo /.../
			Expr expr;
			i = read_expr(source_name, arg, i+4, out expr);
			this.echoes.add(in_start_conditions, expr, source_name, arg, i);
			regex_echo_or_catch = expr;
			if (is_verbose)
			{	stderr.printf("Directive echo at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='c' && arg[i+1]=='a' && arg[i+2]=='t' && arg[i+3]=='c' && arg[i+4]=='h' && !arg[i+5].isalnum() && arg[i+5]!='_')
		{	// catch /.../
			Expr expr;
			i = read_expr(source_name, arg, i+5, out expr);
			this.catches.add(in_start_conditions, expr, source_name, arg, i);
			regex_echo_or_catch = expr;
			if (is_verbose)
			{	stderr.printf("Directive catch at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='i' && arg[i+1]=='n' && arg[i+2]=='i' && arg[i+3]=='t' && !arg[i+4].isalnum() && arg[i+4]!='_')
		{	// init /.../
			i = read_expr(source_name, arg, i+4, out this.init_block);
			if (is_verbose)
			{	stderr.printf("Directive init at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='f' && arg[i+1]=='i' && arg[i+2]=='n' && arg[i+3]=='a' && arg[i+4]=='l' && !arg[i+5].isalnum() && arg[i+5]!='_')
		{	// final /.../
			i = read_expr(source_name, arg, i+5, out this.final_block);
			if (is_verbose)
			{	stderr.printf("Directive final at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='p' && arg[i+1]=='u' && arg[i+2]=='t' && arg[i+3]=='s' && !arg[i+4].isalnum() && arg[i+4]!='_')
		{	// puts /.../
			i = read_expr(source_name, arg, i+4, out this.puts_block);
			if (is_verbose)
			{	stderr.printf("Directive puts at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='p' && arg[i+1]=='u' && arg[i+2]=='t' && arg[i+3]=='c' && !arg[i+4].isalnum() && arg[i+4]!='_')
		{	// putc /.../
			i = read_expr(source_name, arg, i+4, out this.putc_block);
			if (is_verbose)
			{	stderr.printf("Directive putc at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='p' && arg[i+1]=='r' && arg[i+2]=='i' && arg[i+3]=='n' && arg[i+4]=='t' && arg[i+5]=='f' && !arg[i+6].isalnum() && arg[i+6]!='_')
		{	// printf /.../
			i = read_expr(source_name, arg, i+6, out this.printf_block);
			if (is_verbose)
			{	stderr.printf("Directive printf at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='w' && arg[i+1]=='r' && arg[i+2]=='i' && arg[i+3]=='t' && arg[i+4]=='e' && !arg[i+5].isalnum() && arg[i+5]!='_')
		{	// write /.../
			i = read_expr(source_name, arg, i+5, out this.write_block);
			if (is_verbose)
			{	stderr.printf("Directive write at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='d' && arg[i+1]=='e' && arg[i+2]=='f' && !arg[i+3].isalnum() && arg[i+3]!='_')
		{	// def DIGIT /[0-9]/
			i = skip_space(source_name, arg, i+3);
			var from = i;
			while (arg[i].isalnum() || arg[i]=='_') i++;
			var to = i;
			if (to-from==0 || arg[from].isdigit())
			{	throw_e("Expected something like \"def DIGIT /[0-9]/\"", source_name, arg, i);
			}
			var name = arg[from : to];
			i = skip_space(source_name, arg, i);
			char delim;
			i = read_delim(source_name, arg, i, out delim);
			from = i;
			i = skip_pattern_to_delim(source_name, arg, i, delim);
			to = i - 1;
			Flags flags;
			i = read_flags(arg, i, out flags);
			var def = new Pattern.from_arg(null, source_name, arg, ref from, delim, flags);
			this.defs.append_printf("%s\t%s\n", name, def.get_code());
			if (is_verbose)
			{	stderr.printf("Directive def \"%s\" at %s(%d:%d)\n", name, source_name, line, line_char);
			}
		}
		else if (arg[i]=='p' && arg[i+1]=='k' && arg[i+2]=='g' && !arg[i+3].isalnum() && arg[i+3]!='_')
		{	// pkg /foo-1.0 bar.../
			string str;
			Flags flags;
			i = read_delimited(source_name, arg, i+3, out str, out flags, true, false, true);
			for (int j=0; true;)
			{	while (str[j].isspace()) j++;
				var from = j;
				while (str[j]!='\0' && !str[j].isspace()) j++;
				var to = j;
				if (to-from == 0)
				{	break;
				}
				var pkg = Shell.quote(str[from : to]);
				this.pkgs += pkg;
			}
			if (is_verbose)
			{	stderr.printf("Directive pkg at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='c' && arg[i+1]=='o' && arg[i+2]=='m' && arg[i+3]=='p' && arg[i+4]=='i' && arg[i+5]=='l' && arg[i+6]=='e' && !arg[i+7].isalnum() && arg[i+7]!='_')
		{	// compile /filename/
			string str;
			Flags flags;
			i = read_delimited(source_name, arg, i+7, out str, out flags, true, false, true);
			var j = read_filename(str, 0, out this.compile);
			if (this.compile=="" || str[j]!='\0')
			{	throw_e("Filename ([A-Za-z0-9_\\.][A-Za-z0-9_\\.\\-]*) expected after \"compile\"", source_name, arg, i);
			}
			if (is_verbose)
			{	stderr.printf("Directive compile at %s(%d:%d)\n", source_name, line, line_char);
			}
		}
		else if (arg[i]=='u' && arg[i+1]=='s' && arg[i+2]=='e' && !arg[i+3].isalnum() && arg[i+3]!='_')
		{	// use /name1 name2 .../
			string str_all;
			Flags flags;
			i = read_delimited(source_name, arg, i+3, out str_all, out flags, true, false, true);
			if (is_verbose)
			{	stderr.printf("Directive use at %s(%d:%d)\n", source_name, line, line_char);
			}
			string str;
			for (int j=0; true;)
			{	j = read_filename(str_all, j, out str);
				if (str == "") break;
				var len = str.length;
				if (len>5 && str[len-5]=='.' && str[len-4].tolower()=='v' && str[len-3].tolower()=='s' && str[len-2].tolower()=='e' && str[len-1].tolower()=='d')
				{	if (call_level >= 32)
					{	throw_e("Files nesting level is too deep", source_name, arg, i);
					}
					var fh = fopen(str, "r");
					var buf = new uint8[102400];
					while (true)
					{	var read = fh.read(buf);
						if (read < buf.length)
						{	buf[read] = '\0';
							break;
						}
						else
						{	buf.resize((int)read*2);
						}
					}
					parse_arg((string)buf, str, call_level+1);
				}
				else
				{	this.uses += str;
				}
			}
		}
		else if (arg[i] == ';')
		{	i++; // empty directive
		}
		else
		{	was_skipped = false;
		}
		return i;
	}

	private static int skip_space(string source_name, string str, int i, bool with_line_comments=false) throws Exception
	{	assert(!'\0'.isspace());
		while (true)
		{	while (str[i].isspace()) i++;
			if (str[i]=='/' && str[i+1]=='*')
			{	for (i+=2; str[i]!='\0'; i++)
				{	if (str[i]=='*' && str[i+1]=='/')
					{	i += 2;
						break;
					}
					if (str[i]=='/' && str[i+1]=='*')
					{	throw_e("\"/*\" and \"*/\" are not allowed in comments", source_name, str, i);
					}
				}
			}
			else if (with_line_comments && str[i]=='/' && str[i+1]=='/')
			{	while (str[i]!='\r' && str[i]!='\n' && str[i]!='\0') i++;
			}
			else
			{	return i;
			}
		}
	}

	private static bool can_be_delim(char c)
	{	assert(!' '.isgraph() && !'\0'.isgraph());
		return !c.isalnum() && c.isgraph() && c!='_' && c!='\\' && c!=';' && c!='<' && c!='>' && c!=')' && c!='[' && c!=']' && c!='{' && c!='}';
	}

	private static int read_delim(string source_name, string str, int i, out char delim) throws Exception
	{	while (str[i].isspace()) i++;
		if (!can_be_delim(str[i]))
		{	throw_e("Expected delimiter. The delimiter cannot be letters, digits or _\\<>)[]{}", source_name, str, i);
		}
		delim = str[i];
		return i + 1;
	}

	private static int skip_pattern_to_delim(string source_name, string str, int i, char delim) throws Exception
	{	if (delim == '(')
		{	// Only skip and discard the result, because we don't know about flags at this point. Will reread later.
			new Pattern.from_arg(null, source_name, str, ref i, delim, Flags());
			assert(str[i] == ')');
			i++;
			while (str[i].isspace()) i++;
			if (str[i] != '(')
			{	throw_e("Expected ')(' because '(' was used as delimiter. The syntax is: (pattern)(replacement).", source_name, str, i);
			}
		}
		else
		{	for (; str[i]!=delim; i++)
			{	if (str[i] == '\0')
				{	throw_e(@"Expected '$delim' delimiter at end of $source_name", source_name, str, i, false);
				}
				if (str[i]=='\\' && str[i+1]!='\0')
				{	i++;
				}
			}
		}
		return i + 1;
	}

	private static int read_to_delim(string source_name, string str, int i, out string value, bool trim, char delim, bool is_vala) throws Exception
	{	if (trim)
		{	while (str[i].isspace()) i++;
		}
		StringBuilder sb = null;
		int from = i;
		int to = -1;
		if (delim == '(')
		{	delim = ')';
			bool no_eof;
			to = normal_skip(source_name, str, i, ")", is_vala, out no_eof);
			if (!no_eof) to++; else to--; // t++ to get exception (see below)
		}
		for (; to==-1 ? str[i]!=delim : i<to; i++)
		{	if (str[i] == '\0')
			{	throw_e(@"Expected '$delim' delimiter at end of $source_name", source_name, str, i, false);
			}
			if (str[i]=='\\' && str[i+1]!='\0')
			{	if (str[i+1] == delim)
				{	if (sb == null) sb = new StringBuilder();;
					sb.append(str[from : i]);
					sb.append_c(delim);
					from = i+2;
				}
				i++;
			}
		}
		assert(str[i] == delim);
		to = i;
		if (trim)
		{	while (str[to-1].isspace()) to--;
		}
		if (sb == null)
		{	value = str[from : to];
		}
		else
		{	sb.append(str[from : to]);
			value = sb.str;
		}
		return i + 1;
	}

	private static void backtick_to_apos(string str, StringBuilder sb)
	{	for (int i=0; str[i]!='\0'; i++)
		{	if (str[i]=='\\' && str[i+1]!='\0')
			{	i++;
				if (str[i] != '`')
				{	sb.append_c('\\');
				}
				sb.append_c(str[i]);
			}
			else
			{	sb.append_c(str[i]!='`' ? str[i] : '\'');
			}
		}
	}

	private static int read_delimited(string source_name, string str, int i, out string value, out Flags flags, bool trim, bool apply_e_flag, bool unapply_e_flag) throws Exception requires (!(apply_e_flag && unapply_e_flag))
	{	char delim;
		i = read_delim(source_name, str, i, out delim);
		if (delim=='(' && !unapply_e_flag)
		{	apply_e_flag = true;
		}
		i = read_to_delim(source_name, str, i, out value, trim, delim, apply_e_flag); // Skip in Vala mode when the delim is '(', if 'e' is not explicitly unapplied. That means, ignore ')' if it is inside strings or comments
		i = read_flags(str, i, out flags);
		if (apply_e_flag)
		{	flags.e = true;
		}
		if (unapply_e_flag)
		{	flags.e = false;
		}
		if (flags.b)
		{	var sb = new StringBuilder();
			backtick_to_apos(value, sb);
			value = sb.str;
		}
		return i;
	}

	private static int read_flags(string str, int i, out Flags flags) throws Exception
	{	flags = Flags();
		while (str[i].isalnum())
		{	flags.apply(str, str[i]);
			i++;
		}
		return i;
	}

	private static int read_expr(string source_name, string str, int i, out Expr expr) throws Exception
	{	string value;
		Flags flags;
		while (str[i].isspace()) i++;
		int line, line_char;
		calc_location(str, i, out line, out line_char);
		i = read_delimited(source_name, str, i, out value, out flags, false, false, false);
		expr = new Expr(value, flags, source_name, line, line_char);
		return i;
	}

	private static int read_filename(string str, int i, out string filename)
	{	while (str[i].isspace()) i++;
		if (str[i] == '-') // names starting with '-' are command line options
		{	filename = "";
			return i;
		}
		var from = i;
		while (str[i].isalnum() || str[i]=='_' || str[i]=='-' || str[i]=='.') i++;
		filename = str[from : i];
		return i;
	}

	private void add_start_condition(string name)
	{	if (name != "INITIAL")
		{	for (int i=0; i<start_conditions.length; i++)
			{	if (start_conditions[i] == name)
				{	return;
				}
			}
			start_conditions += name;
		}
	}

	private void start_conditions_to_flex(FileStream output_flex)
	{	// %s
		bool printed = false;
		for (int i=0; i<start_conditions.length; i++)
		{	if (start_conditions[i][0] != 'X')
			{	if (!printed)
				{	output_flex.puts("%s ");
					printed = true;
				}
				else
				{	output_flex.putc(' ');
				}
				output_flex.puts(start_conditions[i]);
			}
		}
		if (printed)
		{	output_flex.putc('\n');
		}
		// %x
		printed = false;
		for (int i=0; i<start_conditions.length; i++)
		{	if (start_conditions[i][0] == 'X')
			{	if (!printed)
				{	output_flex.puts("%x ");
					printed = true;
				}
				else
				{	output_flex.putc(' ');
				}
				output_flex.puts(start_conditions[i]);
			}
		}
		if (printed)
		{	output_flex.putc('\n');
		}
	}

	public void to_output(string yy_c_filename, FileStream output_flex, FileStream output_vala, FileStream output_vapi) throws Exception
	{	string all_start_conditions;
		if (start_conditions.length == 0)
		{	all_start_conditions = "INITIAL";
		}
		else
		{	all_start_conditions = "INITIAL," + strarr_join(",", start_conditions);
		}
		bool with_catch = catches.length() > 0;
		// Flex
		string flex_define = flex_debug_enabled ? "#define VSED_FLEX_DEBUG" : "";
		if (catches.has_intermed()) flex_define += "\n\t#define VSED_CATCH_INTERMED";
		output_flex.puts(FLEX_HEADER.replace("$(define)", flex_define).replace("$(flex_options)", flex_options_default ? "full" : flex_options));
		output_flex.puts
		(	!with_catch ?
			"\t#define ECHO vsed_echo(yytext, yyleng)\n\n\tvoid vsed_echo(const gchar*, int);\n" :
			"\t#define ECHO do {vsed_echo(yytext, yyleng, &vsed_error); VSED_HANDLE_ERROR} while (0)\n\t#define YY_DECL int yylex(GError **_vsed_arg_error)\n\n\tvoid vsed_echo(const gchar*, int, GError**);\n"
		);
		if (!with_catch || write_block==null)
		{	output_flex.puts("\tvoid vsed_write(const gchar*, int);\n\n");
		}
		else
		{	output_flex.puts("\tvoid vsed_write(const gchar*, int, GError**);\n\n");
		}
		for (int i=0; i<exprs.length; i++)
		{	output_flex.puts(exprs[i].pattern.get_c_prototype(with_catch));
		}
		output_flex.puts("%}\n\n");
		Pattern.to_output_flex_global(output_flex);
		start_conditions_to_flex(output_flex);
		output_flex.puts(defs.str);
		output_flex.puts("%%\n\n");
		for (int i=0; i<exprs.length; i++)
		{	exprs[i].pattern.to_output_flex(output_flex, with_catch, with_catch_write(), all_start_conditions, eofs.global_eof_pattern_id, echoes.get_eof_echoes(eofs, with_catch));
		}
		output_flex.puts("\n%%\n\n");
		output_flex.puts("const char *vsed_state_name()\n{\tswitch(YY_START)\n\t{\tcase INITIAL: return \"INITIAL\";\n");
		for (int i=0; i<start_conditions.length; i++)
		{	output_flex.printf("\t\tcase %s: return \"%s\";\n", start_conditions[i], start_conditions[i]);
		}
		output_flex.puts("\t}\n\treturn \"(UNKNOWN)\";\n}\n");
		output_flex.puts(catches.to_catch_intermed_string());
		output_flex.puts(FLEX_FOOTER);
		// Vala
		output_vala.puts("namespace Vsed {\n\n");
		for (int i=0; i<v_blocks_global.length; i++)
		{	output_vala.printf("%s\n", v_blocks_global[i].to_vala_string());
		}
		var init_b = "\tbuffers = {};\n\tbuffers_len = 0;\n";
		for (int i=0; i<v_blocks_var_init.length; i++)
		{	init_b += "\t"+v_blocks_var_init[i].to_vala_string()+"\n";
		}
		if (init_block != null)
		{	init_b += init_block.to_vala_string(true);
		}
		var final_b = final_block==null ? "" : final_block.to_vala_string(true);
		var catch_b = catches.to_vala_string_default();
		var puts_b = puts_block==null ? "stdout.puts(s);" : puts_block.to_vala_string(true);
		var putc_b = putc_block==null ? "stdout.putc(c);" : putc_block.to_vala_string(true);
		var printf_b = printf_block==null ? "var args = va_list(); stdout.vprintf(format, args);" : printf_block.to_vala_string(true);
		var write_b = write_block==null ? "fwrite(data, (size_t)n_bytes, stdout);" : write_block.to_vala_string(true);
		output_vala.printf(VALA, init_b, final_b, init_b, final_b, catch_b, puts_b, putc_b, printf_b, write_b, echoes.to_vala_string());
		for (int i=0; i<exprs.length; i++)
		{	output_vala.printf("\t%s\n\t{\t%s\n\t}\n", exprs[i].pattern.get_vala_prototype(with_catch), exprs[i].to_vala_string(true));
		}
		output_vala.puts(catches.to_vala_string_catches());
		output_vala.puts("\n} // end namespace");
		// VAPI
		output_vapi.printf(VAPI, yy_c_filename, all_start_conditions, !with_catch ? "" : " throws GLib.Error");
	}

	public void to_files(string yy_c_filename, string file_flex, string file_vala, string file_vapi) throws Exception
	{	var output_flex = fopen(file_flex, "w");
		var output_vala = fopen(file_vala, "w");
		var output_vapi = fopen(file_vapi, "w");
		to_output(yy_c_filename, output_flex, output_vala, output_vapi);
	}
}

void calc_location(string source, int pos, out int line, out int line_char)
{	line_char = 1;
	int line_r = 1;
	int line_n = 1;
	for (int i=0; i<pos; i++)
	{	assert(source[i] != '\0');
		if (source[i] == '\r')
		{	line_r++;
			line_char = 1;
		}
		else if (source[i] == '\n')
		{	line_n++;
			line_char = 1;
		}
		else
		{	line_char++;
		}
	}
	line = line_r>line_n ? line_r : line_n;
}

void throw_e(string message, string source_name, string source, int pos, bool with_source_sample=true) throws Exception
{	int line, line_char;
	calc_location(source, pos, out line, out line_char);
	if (!with_source_sample)
	{	throw new Exception.E(@"*ERROR in $source_name($line:$line_char). $message");
	}
	int len = source.length;
	if (len-pos <= 100)
	{	throw new Exception.E(@"*ERROR in $source_name($line:$line_char). $message: $(source[pos : len])");
	}
	else
	{	throw new Exception.E(@"*ERROR in $source_name($line:$line_char). $message: $(source[pos : pos+100])...");
	}
}

FileStream fopen(string filename, string mode) throws Exception
{	var fh = FileStream.open(filename, mode);
	if (fh == null)
	{	throw new Exception.E(@"Failed to open file $filename: $(strerror(errno))");
	}
	return fh;
}

FileStream popen(string filename, string mode) throws Exception
{	var fh = (GLib.FileStream)Posix.FILE.popen(filename, mode);
	if (fh == null)
	{	throw new Exception.E(@"Failed to open pipe $filename: $(strerror(errno))");
	}
	return fh;
}

void system(string command) throws Exception
{	if (Posix.system(command) != 0)
	{	throw new Exception.E(@"Failed to execute command: $command");
	}
}

int main(string[] args)
{	bool keep_sources = false;
	int retval = 1;
	var dir = Environment.get_tmp_dir();
	var prefix = @"vsed_$(Random.next_int())";
	var prog_name = "";
	try
	{	var result = new Result(args);
		if (result.compile != null)
		{	dir = ".";
			prefix = "";
			prog_name = result.compile;
			keep_sources = true;
		}
		result.to_files(@"$dir/$prefix$prog_name.yy.c", @"$dir/$prefix$prog_name.flex", @"$dir/$prefix$prog_name.vala", @"$dir/$prefix$prog_name.vapi");
		var run_lex = @"flex -o $dir/$prefix$prog_name.yy.c $dir/$prefix$prog_name.flex";
		var run_valac = @"valac -d $dir -o $prefix$prog_name --vapidir=$dir -X -I. $(result.get_valac_args()) --pkg $prefix$prog_name $dir/$prefix$prog_name.vala";
		system(run_lex);
		system(run_valac);
		if (keep_sources)
		{	fopen("_Makefile", "w").puts(MAKEFILE.replace("$(prog_name)", prog_name).replace("$(run_lex)", run_lex).replace("$(run_valac)", run_valac).replace("$(pkgs)", result.get_pkgs()));
		}
		else
		{	var program = popen(@"$dir/$prefix$prog_name", "w");
			var buf = new uint8[10240];
			while (true)
			{	var read = stdin.read(buf);
				if (read < buf.length)
				{	if (read > 0)
					{	buf.resize((int)read);
						program.write(buf);
					}
					break;
				}
				program.write(buf);
			}
		}
		retval = 0;
	}
	catch (Exception e)
	{	stderr.puts(e.message);
		stderr.puts("\n");
	}
	if (!keep_sources)
	{	FileUtils.unlink(@"$dir/$prefix$prog_name.flex");
		FileUtils.unlink(@"$dir/$prefix$prog_name.vala");
		FileUtils.unlink(@"$dir/$prefix$prog_name.vapi");
		FileUtils.unlink(@"$dir/$prefix$prog_name.yy.c");
		FileUtils.unlink(@"$dir/$prefix$prog_name");
	}
	return retval;
}

} // end namespace
